﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;

namespace LotroData
{
    /// <summary>
    /// Stores credentials required for making a LotroXmlRequest
    /// </summary>
    public class LotroCredential
    {
        /// <summary>
        /// Represents the extension that a credential file will use.
        /// </summary>
        public const string Extension = ".ldc";

        /// <summary>
        /// Gets the name of the developer.
        /// </summary>
        /// <value>The name of the developer as a <see cref="System.String"/>.</value>
        public string DeveloperName { get; private set; }

        /// <summary>
        /// Gets the API key.
        /// </summary>
        /// <value>The API key as a <see cref="System.String"/>.</value>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ApiKey",
            Justification="Microsoft requires Pascal cassing for 3-letter acronymns, but doesn't recognise API")]
        public string ApiKey { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance is encrypted.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is encrypted; otherwise, <c>false</c>.
        /// </value>
        public bool IsEncrypted { get; private set; }

        /// <summary>
        /// Gets the on-disk path to the credentials file.
        /// If the credential has not been saved to disk, this will be null.
        /// </summary>
        /// <value>
        /// A <see cref="System.String"/> value the represents the path.
        /// </value>
        public string Path { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="LotroCredential"/> class.
        /// </summary>
        /// <exception cref="ArgumentNullException">Occurs when a passed parameter is null.</exception>
        /// <param name="devname">The developer name as a <see cref="System.String"/>.</param>
        /// <param name="apikey">The API key as a <see cref="System.String"/>.</param>
        /// <param name="encrypt">if set to <c>true</c>, encrypts this credential.</param>
        public LotroCredential(string devname, string apikey, bool encrypt)
        {
            if (string.IsNullOrEmpty(devname)) throw new ArgumentNullException("devname");
            if (string.IsNullOrEmpty(apikey)) throw new ArgumentNullException("apikey");

            DeveloperName = (encrypt) ? devname.Encrypt() : devname;
            ApiKey = (encrypt) ? apikey.Encrypt() : apikey;
            IsEncrypted = encrypt;
            Path = string.Empty;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LotroCredential"/> class.
        /// </summary>
        /// <param name="devname">The developer name as a <see cref="System.String"/>.</param>
        /// <param name="apikey">The API key as a <see cref="System.String"/>.</param>
        /// <param name="isEncrypted">if set to <c>true</c>, indicates the credentials are encrypted.</param>
        /// <param name="path">The on-disk path.</param>
        private LotroCredential(string devname, string apikey,bool isEncrypted, string path)
        {
            DeveloperName = devname;
            ApiKey = apikey;
            IsEncrypted = isEncrypted;
            Path = path;
        }

        /// <summary>
        /// Loads a credential from a file
        /// </summary>
        /// <exception cref="ArgumentNullException">Occurs when the path is null.</exception>
        /// <param name="path">The fully qualified path to the file.</param>
        /// <returns></returns>
        public static LotroCredential FromXml(string path)
        {
            if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");

            //Local variables
            string devname, apikey;
            bool encrypted;

            try
            {
                if (path.Substring(path.Length - 4) != LotroCredential.Extension)
                    path += LotroCredential.Extension;

                var xDoc = XDocument.Load(path);

                var cred = xDoc.Element("credentials");

                encrypted = bool.Parse(cred.Attribute("encrypted").Value);
                devname = cred.Element("devname").Value;
                apikey = cred.Element("apikey").Value;

                return new LotroCredential(devname, apikey, encrypted, path);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Saves the credentials to an XML document.
        /// </summary>
        /// <exception cref="ArgumentNullException">Occurs when the path is null.</exception>
        /// <param name="path">The fully qualified path to save the file to.</param>
        /// <param name="encrypt">Denotes whether the credentials should be encrypted, if they already aren't.</param>
        public void ToXml(string path, bool encrypt)
        {
            if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");

            var devName = (encrypt == true) && (IsEncrypted == false) ? DeveloperName.Encrypt() : DeveloperName;
            var apikey = (encrypt == true) && (IsEncrypted == false) ? ApiKey.Encrypt() : ApiKey;
            var encrypted = IsEncrypted || encrypt;

            try
            {

                var xDoc = new XDocument(
                    new XDeclaration("1.0", "UTF-8", "yes"),
                    new XElement("credentials",
                            new XAttribute("encrypted", encrypted.ToString(CultureInfo.InvariantCulture)),
                            new XElement("devname", devName.ToString(CultureInfo.InvariantCulture)),
                            new XElement("apikey", apikey.ToString(CultureInfo.InvariantCulture))));

                var writer = XmlWriter.Create(
                    new FileStream(path, FileMode.Create, FileAccess.ReadWrite)
                    , new XmlWriterSettings()
                    {
                        Indent = true,
                        CloseOutput = true
                    });

                xDoc.WriteTo(writer);

                writer.Flush();
                writer.Close();
                xDoc = null;
            }
            catch (IOException)
            {
                throw;
            }
        }
    }
}
